using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace Com.Guocz.Media.FileSystem
{
    class IndexManager
    {
        private ChunkManager indexChunk;
        private Crypt.ICrypt cryptor;
        public IndexManager(MetaInfo meta, Crypt.ICrypt cryptor)
        {
            this.cryptor = cryptor;
            indexChunk = ChunkManager.GetChunkManager(meta.IndexFile);
            initFS();
        }

        private void initFS()
        {
            try
            {
                indexChunk.read(0);
            }
            catch (InvalidArgumentException e)
            {
                if (e.Message.IndexOf("can't be read yet") != -1)
                {
                    Path root = new Path();
                    root.Name = "";
                    Store(root);
                }
            }
        }

        public Path Load()
        {
            Path root = new Path();

            IList<int> blocks = GetBlockList();
            List<byte> bufList = new List<byte>();
            foreach (var block in blocks)
            {
                byte[] buf = indexChunk.read(block);
                buf = cryptor.Decrypt(buf);
                foreach (var b in buf)
                {
                    bufList.Add(b);
                }
            }
            MemoryStream stream = new MemoryStream(bufList.ToArray());
            BinaryReader reader = new BinaryReader(stream);
            root.readField(reader);
            reader.Close();
            stream.Close();

            return root;
        }

        private IList<int> GetBlockList()
        {
            byte[] extrainfo = indexChunk.read(0);
            valid(extrainfo);

            byte[] blockinfo = indexChunk.read(1);
            blockinfo = cryptor.Decrypt(blockinfo);
            MemoryStream stream = new MemoryStream(blockinfo);
            BinaryReader reader = new BinaryReader(stream);
            int count = reader.ReadInt32();
            IList<int> blocks = new List<int>(count);
            for (int i = 0; i < count; i++)
            {
                blocks.Add(reader.ReadInt32());
            }
            reader.Close();
            stream.Close();
            return blocks;
        }

        private void valid(byte[] extrainfo)
        {
            try
            {
                extrainfo = cryptor.Decrypt(extrainfo);
                if (extrainfo.Length == MAGIC_HEADER.Length)
                {
                    for (int i = 0; i < extrainfo.Length; i++)
                    {
                        if (extrainfo[i] != MAGIC_HEADER[i])
                        {
                            throw new InvalidArgumentException();
                        }
                    }
                }

            }
            catch (Exception e)
            {
                throw new InvalidArgumentException();
            }
        }

        internal void Store(Path root)
        {
            saveHeader(root);
            indexChunk.clear(2);
            List<int> blocks = saveData(root);

            MemoryStream stream = new MemoryStream();
            BinaryWriter writer = new BinaryWriter(stream);
            writer.Write(blocks.Count);
            for (int i = 0; i < blocks.Count; i++)
            {
                writer.Write(blocks[i]);
            }
            writer.Close();
            byte[] buf = stream.ToArray();
            stream.Close();

            byte[] secret = cryptor.Encrypt(buf);
            indexChunk.write(secret, 1);
        }

        private List<int> saveData(Path root)
        {
            List<int> blocks = new List<int>();

            MemoryStream stream = new MemoryStream();
            BinaryWriter writer = new BinaryWriter(stream);
            root.writeField(writer);
            
            byte[] buf = new byte[Const.BLOCK_DATA_SIZE];
            stream.Seek(0, SeekOrigin.Begin);
            while (stream.Position < stream.Length)
            {
                int read = stream.Read(buf, 0, buf.Length);
                byte[] plain = new byte[read];
                Array.Copy(buf, 0, plain, 0, plain.Length);
                byte[] secret = cryptor.Encrypt(plain);
                blocks.Add(indexChunk.write(secret));
            }
            writer.Close();
            stream.Close();
            return blocks;
        }

        private static byte[] MAGIC_HEADER = new byte[] { 19, 84, 2, 25 };
        private void saveHeader(Path root)
        {
            byte[] tmp = cryptor.Encrypt(MAGIC_HEADER);
            indexChunk.write(tmp, 0);
        }
    }
}
